﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using System.Text;
using Jay.Sc2.Bof.Lib.Action.Terran;
using Jay.Sc2.Bof.Lib.Action.Zerg;
using Jay.Sc2.Bof.Lib.Action.Protoss;
using Jay.Sc2.Bof.Lib.Tools;
using System.IO;

namespace Jay.Sc2.Bof.Lib.Action
{
	public class ActionList : List<IAction>, IActionList
	{
		private static Random m_Random = new Random();
		private IRace m_Race;
		public IRace Race { get { return m_Race; } }
		public ActionList(IRace race) { m_Race = race; }
		public ActionList(IRace race, IEnumerable<IAction> collection) : base(collection) { m_Race = race; }
		public ActionList(IActionList actionList) : base(actionList) { m_Race = actionList.Race; }
		public bool HaveRequirements(int index, IAction action)
		{
			IAction[,] variable = action.Requirements;
			if (variable == null) return true;
			for (int c = 0; c < variable.GetLength(0); c++)
			{
				bool allFound = true;
				for (int d = 0; d < variable.GetLength(1) && allFound; d++)
				{
					int found = IndexOf(variable[c, d]);
					allFound = (found >= 0 && found < index);
				}
				if (allFound)
					return true;
			}
			return false;
		}
		public bool HaveInputs(int index, IAction action)
		{
			IAction[,] inputs = action.Inputs;
			if (inputs == null) return true;
			for (int c = 0; c < inputs.GetLength(0); c++)
			{
				Dictionary<IAction, int> needed = new Dictionary<IAction, int>();
				Dictionary<IAction, Range> existings = new Dictionary<IAction, Range>();
				for (int d = 0; d < inputs.GetLength(1); d++)
				{
					int i = 0;
					IAction input = inputs[c, d];
					if (!existings.ContainsKey(input))
						existings[input] = CheckInput(index, input);
					else
						needed.TryGetValue(input, out i);
					i++;
					needed[input] = i;
				}
				bool allOk = true;
				foreach (KeyValuePair<IAction, int> keyVal in needed)
				{
					Range inputRange = existings[keyVal.Key];
					inputRange.Minimum -= keyVal.Value;
					inputRange.Maximum -= keyVal.Value;
					if (inputRange.Maximum < 0)
					{
						allOk = false;
						break;
					}
				}
				if (allOk) return true;
			}
			return false;
		}
		public int InsertAction(int index, IAction action)
		{
			if (!HaveInputs(index, action))
			{
				IAction[,] input = action.Inputs;
				int rand = m_Random.Next(input.GetLength(0));
				for (int c = 0; c < input.GetLength(1); c++)
				{
					IAction required = input[rand, c];
					index = InsertAction(index, required) + 1;
				}
			}
			if (!HaveRequirements(index, action))
			{
				IAction[,] variable = action.Requirements;
				int rand = m_Random.Next(action.Requirements.GetLength(0));
				for (int c = 0; c < variable.GetLength(1); c++)
				{
					IAction required = variable[rand, c];
					int indexOf = IndexOf(required);
					if (indexOf < 0 || indexOf >= index)
						index = InsertAction(index, required) + 1;
				}
			}
			Insert(index, action);
			return index;
		}
		public void AddAction(IAction action) { InsertAction(Count, action); }
		public void AddMissingRequirements()
		{
			HashSet<IAction> actionsPresent = new HashSet<IAction>();
			for (int c = 0; c < Count; c++)
			{
				IAction action = this[c];
				if (!actionsPresent.Contains(action) && !HaveRequirements(c, action))
				{
					RemoveAt(c);
					InsertAction(c, action);
				}
				actionsPresent.Add(action);
			}
		}
		public void Save(XmlNode node, XmlDocument document)
		{
			if (m_Race != null)
			{
				XmlAttribute raceAttribute = document.CreateAttribute("Race");
				node.Attributes.Append(raceAttribute);
				raceAttribute.Value = m_Race.Name;
			}
			foreach (IAction action in this)
			{
				XmlElement actionNode = document.CreateElement("Action");
				XmlAttribute typeAttribute = document.CreateAttribute("Type");
				node.AppendChild(actionNode);
				actionNode.Attributes.Append(typeAttribute);
				typeAttribute.Value = action.GetType().FullName;
			}
		}
		public void Load(XmlNode node)
		{
			XmlAttribute raceAttribute = node.Attributes["Race"];
			if (raceAttribute != null)
			{
				switch (raceAttribute.Value)
				{
					case "Terran": m_Race = TerranRace.Instance; break;
					case "Zerg": m_Race = ZergRace.Instance; break;
					case "Protoss": m_Race = ProtossRace.Instance; break;
				}
			}
			XmlNodeList actionNodes = node.SelectNodes("Action");
			foreach (XmlNode actionNode in actionNodes)
			{
				string type = actionNode.Attributes["Type"].Value;
				Type classType = Type.GetType(type);
				FieldInfo fieldInfo = classType.GetField("Instance");
				IAction action = fieldInfo.GetValue(null) as IAction;
				AddAction(action);
			}
		}
		public void Save(string filename)
		{
			string extension = Path.GetExtension(filename).ToLower();
			if (extension == ".txt")
			{
				string description = ShortDescription;
				StreamWriter writer = new StreamWriter(File.Open(filename, FileMode.Create));
				writer.Write(description);
				writer.Close();
			}
			else
			{
				XmlDocument document = new XmlDocument();
				XmlElement actionList = document.CreateElement("ActionList");
				Save(actionList, document);
				document.AppendChild(actionList);
				document.Save(filename);
			}
		}
		public void Load(string filename)
		{
			string extension = Path.GetExtension(filename).ToLower();
			if (extension == ".txt")
			{
				StreamReader reader = new StreamReader(File.OpenRead(filename));
				string description = reader.ReadToEnd();
				LoadFromShortDescription(description);
				reader.Close();
			}
			else
			{
				XmlDocument document = new XmlDocument();
				document.Load(filename);
				XmlNode actionList = document.SelectSingleNode("ActionList");
				if (actionList != null) Load(actionList);
			}
		}
		public void Cut(int index)
		{
			while (Count > index)
				RemoveAt(index);
		}

		public bool Equals(IActionList other)
		{
			if (Count != other.Count) return false;
			for (int c = 0; c < Count; c++)
				if (!this[c].Equals(other[c])) return false;
			return true;
		}

		public Range CheckInput(int index, IAction action)
		{
			Range result = null;
			IActionList initial = m_Race.InitialActions;
			if (this != initial) result = initial.CheckInput(int.MaxValue, action);
			else result = new Range();
			if(index > Count) index = Count;
			for (int c = 0; c < index; c++)
			{
				if (this[c] == action)
				{
					result.Maximum++;
					result.Minimum++;
				}
				else
				{
					Range range = this[c].InputRangeOf(action);
					result.Minimum -= range.Maximum;
					result.Maximum -= range.Minimum;
				}
			}
			return result;
		}

		public int CountAction(IAction action)
		{
			int total = 0;
			for (int c = 0; c < Count; c++)
				if (this[c] == action)
					total++;
			return total;
		}
		public int AppendMissingAndRemoveExceeding(IList<IAction> source)
		{
			if(source == null || source.Count == 0) return 0;
			int added = 0;
			Dictionary<IAction, int> counter = new Dictionary<IAction, int>();
			foreach (IAction action in source)
			{
				int value = 0;
				if (counter.TryGetValue(action, out value))
				{
					value++;
					counter[action] = value;
				}
				else
				{
					counter.Add(action, 1);
				}
			}
			for (int c=0;c<Count;c++)
			{
				IAction action = this[c];
				int value = 0;
				if (counter.TryGetValue(action, out value))
				{
					value--;
					counter[action] = value;
					//if (value < 0)
					//{
					//   RemoveAt(c);
					//   c--;
					//}
				}
			}
			foreach (KeyValuePair<IAction,int> kvp in counter)
			{
				if (kvp.Value > 0)
				{
					added += kvp.Value;
					for (int c = 0; c < kvp.Value; c++)
						AddAction(kvp.Key);
				}
			}
			return added;
		}

		public int LoadFromShortDescription(string shortDescription)
		{
			string[] lines = shortDescription.Split('\n');
			int lastCount = 6;
			int countAction = 0;
			foreach (string line in lines)
			{
				int count = lastCount;
				string[] split = line.Split(' ', '\t');
				if (split.Length == 2)
				{
					int.TryParse(split[0].Trim(), out count);
					split[0] = split[1];
				}
				string actionName = split[0].Trim();
				while (lastCount < count)
				{
					AddAction(m_Race.ActionProduceHarvester);
					countAction++;
					lastCount++;
				}
				foreach (IAction action in m_Race.Actions)
				{
					if (action.CleanName == actionName || action.Name == actionName)
					{
						AddAction(action);
						countAction++;
						ActionProduce produce = action as ActionProduce;
						if(produce != null)
							lastCount += produce.Food;
						break;
					}
				}
			}
			return countAction;
		}

		public string ShortDescription
		{
			get
			{
				StringBuilder builder = new StringBuilder();
				int foodCount = 6;
				int lastCount = -1;
				foreach (IAction action in this)
				{
					if (action != ActionProduceSCV.Instance && action != ActionProduceDrone.Instance && action != ActionProduceProbe.Instance)
					{
						if (foodCount != lastCount) builder.Append(foodCount.ToString() + "\t");
						else builder.Append("\t");
						lastCount = foodCount;
						builder.AppendLine(action.CleanName);
					}
					ActionProduce produce = action as ActionProduce;
					if(produce != null) foodCount += produce.Food;
				}
				return builder.ToString();
			}
		}


		//public static List<IActionList> GenerateVariablesActionLists(List<IAction> actions)
		//{
		//   List<IActionList> actionLists = new List<IActionList>();
		//   actionLists.Add(new ActionList());
		//   return GenerateVariablesActionLists(actionLists, actions);
		//}
		//public static List<IActionList> GenerateVariablesActionLists(List<IActionList> actionLists, List<IAction> actions)
		//{
		//   Stack<IAction> stack = new Stack<IAction>();
		//   foreach (IAction action in actions)
		//      actionLists = GenerateVariablesActionLists(actionLists, action, true, stack);
		//   return actionLists;
		//}
		//public static List<IActionList> GenerateVariablesActionLists(List<IActionList> actionLists ,IAction action, bool addEvenIfPresent, Stack<IAction> stack)
		//{
		//   List<IActionList> results = new List<IActionList>();
		//   if (stack.Contains(action)) return results;
		//   stack.Push(action);
		//   foreach (IActionList actionList in actionLists)
		//   {
		//      if (!actionList.Contains(action))
		//      {
		//         List<IActionList> temp = new List<IActionList>();
		//         temp.Add(actionList);
		//         //IAction[] requirements = action.Requirements;
		//         //if (requirements != null)
		//         //{
		//         //   foreach (IAction required in requirements)
		//         //   {
		//         //      temp = GenerateVariablesActionLists(temp, required, false, stack);
		//         //   }
		//         //}
		//         IAction[,] variableRequirements = action.Requirements;
		//         if (variableRequirements != null)
		//         {
		//            for (int c = 0; c < variableRequirements.GetLength(0); c++)
		//            {
		//               List<IActionList> tempCopy = new List<IActionList>(temp);
		//               for (int d = 0; d < variableRequirements.GetLength(1); d++)
		//               {
		//                  tempCopy = GenerateVariablesActionLists(tempCopy, variableRequirements[c, d], false, stack);
		//               }
		//               results.AddRange(CloneAndAddAction(tempCopy, action));
		//            }
		//         }
		//         else
		//            results.AddRange(CloneAndAddAction(temp, action));
		//      }
		//      else if (addEvenIfPresent)
		//      {
		//         IActionList clone = new ActionList(actionList);
		//         clone.Add(action);
		//         results.Add(clone);
		//      }
		//      else
		//         results.Add(actionList);
		//   }
		//   if(stack.Pop() != action) throw new Exception();
		//   return results;
		//}
		//public static List<IActionList> CloneAndAddAction(List<IActionList> actionLists, IAction action)
		//{
		//   List<IActionList> results = new List<IActionList>();
		//   foreach (IActionList actionList in actionLists)
		//   {
		//      IActionList clone = new ActionList(actionList);
		//      clone.Add(action);
		//      results.Add(clone);
		//   }
		//   return results;
		//}
	}
}
